package com.yanyu.space.common.core.exception;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import com.yanyu.space.common.core.bean.web.R;
import com.yanyu.space.common.core.util.WebUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author yanyu
 * 全局异常
 */
@Slf4j
@RestControllerAdvice
@Order(value = Ordered.LOWEST_PRECEDENCE)
public class GlobalExceptionHandler {

    /**
     * 处理请求参数格式错误 @RequestParam上validate失败后抛出该异常 public Result test3( @RequestParam @NotEmpty String name)
     */
    @ResponseStatus(value= HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = ConstraintViolationException.class)
    public R handlerConstraintViolationException(ConstraintViolationException e) {
        Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
        Map<String, String> errMap = new HashMap<>(constraintViolations.size());
        constraintViolations.forEach(constraintViolation -> {
            errMap.put(constraintViolation.getPropertyPath().toString(), constraintViolation.getMessage());
        });
        log.error("error by handlerConstraintViolationException , url {}, method {}", WebUtil.getUrl(), WebUtil.getMethod(), e);
        return R.fail(new ParamException(), errMap);
    }

    /**
     * BindException异常：处理form表单提交 在实体类使用前@validate 验证路径中请求实体校验失败后抛出该异常 public Result test2( @Validated Student student)
     * MethodArgumentNotValidException异常：处理请求参数格式错误 @RequestBody上validate失败后抛出该异常。public Result test1(@RequestBody @Validated Student student)
     */
    @ResponseStatus(value= HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = {BindException.class, MethodArgumentNotValidException.class})
    public R handleBindException(Exception e) {
        String errorMsg = getBindingResultErrorMessage(e);
        log.error("error by handleBindException , url {}, method {}", WebUtil.getUrl(), WebUtil.getMethod(), e);
        return R.fail(new ParamException(), errorMsg);
    }

    /**
     * 其他异常
     */
    @ResponseStatus(value= HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Exception.class)
    public R handleException(Exception e) {
        log.error("error by handleException , url {}, method {}", WebUtil.getUrl(), WebUtil.getMethod(), e);
        if(e instanceof NotLoginException) {
            return R.fail(new AuthException());
        } else if(e instanceof NotPermissionException || e instanceof NotRoleException) {
            return R.fail(new PermissionException());
        }
        return R.fail(new ServiceException());
    }

    /**
     * 自定义异常
     */
    @ResponseStatus(value= HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(ServiceException.class)
    public R handleGeneralException(ServiceException e) {
        log.error("error by handleGeneralException , url {}, method {}", WebUtil.getUrl(), WebUtil.getMethod(), e);
        return R.fail(e);
    }

    private String getBindingResultErrorMessage(Throwable error) {
        StringBuilder sb = new StringBuilder();
        BindingResult result = extractBindingResult(error);
        result.getFieldErrors().forEach((fieldError) -> {
            sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(",");
        });
        return sb.toString();
    }

    private BindingResult extractBindingResult(Throwable error) {
        if (error instanceof BindingResult) {
            return (BindingResult) error;
        }
        if (error instanceof MethodArgumentNotValidException) {
            return ((MethodArgumentNotValidException) error).getBindingResult();
        }
        return null;
    }
}
